<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Tree demo</title>

<link rel="stylesheet" type="text/css" href="../theme-remote.css" />
<style type="text/css">
body {
    font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
    font-size: 11px;
}

#debugPanel pre {
    font-family: "Lucida Console", "Courier New", Courier, monospace;
}

div.container {
    border: 0px solid green;
    margin: 10px;
}

.displayNone {
    display: none;
}

.invisible {
    visibility: hidden;
}
</style>

<script type="text/javascript">
var djConfig = {
    isDebug: true,
    parseOnLoad: true,
    locale: "zh",
    extraLocale: ["en"],
    baseUrl: "../lib/dojotoolkit/dojo/",
    modulePaths: {
        "demo": "../../../script/demo",
        "com": "../../../script/com",
        "dojox.data.AndOrReadStore": "../../../script/dojo-1.4.3-fix/dojox.data/AndOrReadStore",
        "dojox.data.AndOrWriteStore": "../../../script/dojo-1.4.3-fix/dojox.data/AndOrWriteStore"
    }
};
</script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/dojo/1.4.3/dojo/dojo.xd.js.uncompressed.js"></script>
<script type="text/javascript">
    dojo.require("dojo.data.ItemFileWriteStore");
    dojo.require("dojo.date.locale");
    dojo.require("dojo.parser");

    dojo.require("dijit.dijit"); // optimize: load dijit layer
    dojo.require("dijit.ColorPalette");
    dojo.require("dijit.Dialog");
    dojo.require("dijit.form.CheckBox");
    dojo.require("dijit.form.ComboBox");
    dojo.require("dijit.form.Button");
    dojo.require("dijit.form.DateTextBox");
    dojo.require("dijit.form.DropDownButton");
    dojo.require("dijit.form.FilteringSelect");
    dojo.require("dijit.form.Form");
    dojo.require("dijit.form.NumberSpinner");
    dojo.require("dijit.form.NumberTextBox");
    dojo.require("dijit.form.RadioButton");
    dojo.require("dijit.form.SimpleTextarea");
    dojo.require("dijit.form.Textarea");
    dojo.require("dijit.form.TextBox");
    dojo.require("dijit.form.ValidationTextBox");
    dojo.require("dijit.InlineEditBox");
    dojo.require("dijit.layout.BorderContainer");
    dojo.require("dijit.layout.ContentPane");
    dojo.require("dijit.Menu");
    dojo.require("dijit.TitlePane");
    dojo.require("dijit.TooltipDialog");

    dojo.require("dojox.data.AndOrWriteStore");
    dojo.require("dojox.fx");
    dojo.require("dojox.html.entities");
    dojo.require("dojox.layout.FloatingPane");
    dojo.require("dojox.layout.ResizeHandle");
    dojo.require("dojox.widget.Standby");

    dojo.require("dijit.tree.ForestStoreModel");
    dojo.require("dijit.tree.TreeStoreModel");
    dojo.require("dijit.Tree");
</script>
<script type="text/javascript" src="../script/utils.js"></script>
<script type="text/javascript" src="../script/mapping.js"></script>
<script type="text/javascript">
function debug(messages) {
    var msg = demo.utils._getDebugMsg.apply(null, arguments);
    console.debug(msg);

    var msgDiv = dojo.byId("debugPanelContent");
    msgDiv.innerHTML = msg;
}

function append(messages) {
    var msg = demo.utils._getDebugMsg.apply(null, arguments);

    var msgDiv = dojo.byId("debugPanelContent");
    msgDiv.innerHTML += "\n" + msg;
}

function show(id) {
    demo.utils.show(id);
}

function hide(id) {
    demo.utils.hide(id);
}
</script>
<style type="text/css">
@import "../css/rounded-button.css";
@import "../css/rounded-corners.css";

body {
    padding: 10px;
}

.infoLabel {
    font-weight: bold;
}

/* selectable component tree node icon style */
.SelectableComponent {
    background: url("../images/icons/icon-system-status-ok.gif") scroll 0 0 no-repeat;
}

/* non selectable component tree node icon style */
.NonSelectableComponent {
    background: url("../images/icons/icon-sametime-dnd.gif") scroll 0 0 no-repeat;
}

/* inactive component tree node label style */
.InactiveComponent {
    color: #808080;
}

.highlight {
    font-style: oblique;
    background-color: #FFFFE0;
}

.search-status {
    font-style: italic;
    color: blue;
}
</style>
<script type="text/javascript">

//////////////////////////////
dojo.addOnLoad(function() {
    debug("loaded");

    dojo.connect(window, "onresize", dijit.byId("targetComponentContainer"), "resize");
    dojo.addClass(dijit.byId("targetComponentContainer").domNode, "displayNone");

    var divisionStore = new dojox.data.AndOrWriteStore({url: "../data/divisions.json"});
    var divisionSelector = dijit.byId("opcDivision");
    divisionSelector.attr("store", divisionStore);

    // update group selector when user selects a division
    dojo.connect(divisionSelector, "onChange", function(value) {
        var divisionItem = divisionSelector.attr("item");
        if (!divisionItem) {
            return;
        }

        var store = divisionSelector.store;
        var division = store.getIdentity(divisionItem);
        updateGroupSelector(division);
    });

    // update target component tree when user selects a group
    dojo.connect(dijit.byId("opcGroup"), "onChange", function(value) {
        // show group info
        var groupSelector = dijit.byId("opcGroup");
        var groupItem = groupSelector.attr("item");
        if (!groupItem) {
            return;
        }
        showGroupInfo(groupItem, groupSelector.store);

        // refresh target component tree
        var group = getGroup();
        updateTargetComponentTree(group);
    });

    // refresh target component tree when user toggles the show active only checkbox
    dojo.connect(dijit.byId("showActiveOnlyCheckBox"), "onClick", function(e) {
        refreshTargetComponentTree();
    });

    // clear search status label when user types in the search box
    dojo.connect(dijit.byId("searchBox"), "onKeyUp", function(e) {
        setSearchStatus(SearchStatus.DEFAULT);
    });

    // clear search status label when the values in search box changes
    dojo.connect(dijit.byId("searchBox"), "onChange", function(value) {
        setSearchStatus(SearchStatus.DEFAULT);
    });
});

//////////////////////////////
/**
 * Format target component status to display string.
 */
function formatTargetComponentStatus(value) {
    var mapping = {
        "A": "Active Status",
        "C": "Pending Changes",
        "F": "Full Status",
        "I": "Inactive Status",
        "H": "Historical",
        "null": "Null"
    };
    
    var mappedValue = demo.mapping.getMappedValue(value, mapping);
    return mappedValue;
}

/**
 * Format target component type to display string.
 */
function formatTargetComponentType(value) {
    var mapping = {
        "R": "Regular",
        "U": "User Defined"
    };
    
    var mappedValue = demo.mapping.getMappedValue(value, mapping);
    return mappedValue;
};

/**
 * Format target component selectable to display string.
 */
function formatTargetComponentSelectable(value) {
    var mapping = {
        "true": "Yes",
        "false": "No"
    };
    
    var mappedValue = demo.mapping.getMappedValue(value, mapping);
    return mappedValue;
}

/**
 * Create a new <code>dojox.data.AndOrWriteStore</code> with the supplied items.
 */
function createStore(items) {
    var store = new dojox.data.AndOrWriteStore({
        data: {
            identifier: "moniker",
            label: "name",
            items: items
        }
    });

    return store;
}

/**
 * Fetch items from the data store and invoke callback when completed.
 * 
 * @param def
 *          The dojo.Deffered object for signal error callback.
 * @param store
 *          The data store.
 * @param query
 *          The query object to filter on the fetch. Set it to null if you want to fetch all items.
 * @param onComplete
 *          The callback function to be called when fetch is completed.
 */
function doFetchStore(def, store, query, onComplete) {
    var fetchArgs = {
        queryOptions: {
            deep: true
        },
        onError: function(err) {
            console.error(err);
            if (def) {
                def.errback(err);
            }
        }
    };

    fetchArgs.query = query || {name: "*"};
    fetchArgs.onComplete = onComplete;
    store.fetch(fetchArgs);
}

///////////////////////////////////////////////////////////////////
// SearchStatus
///////////////////////////////////////////////////////////////////

/**
 * Search status code
 */
var SearchStatus = {
    DEFAULT: 0,
    SEARCHING: 1,
    NOT_FOUND: -1
};

/**
 * Set the search status. It will update the status string below the search box.
 * @param {SearchStatus} searchStatus
 */
function setSearchStatus(searchStatus) {
    var node = dojo.byId("searchStatus");

    var hasValue = dijit.byId("searchBox").attr("value") ? true: false;

    switch(searchStatus) {
    case SearchStatus.SEARCHING:
        node.innerHTML = "searching";
        dojo.removeClass(node, "invisible");
        dijit.byId("searchBox").attr("disabled", true);
        dijit.byId("includeDescriptionCheckBox").attr("disabled", true);
        dijit.byId("findNextButton").attr("disabled", true);
        dijit.byId("findAllButton").attr("disabled", true);
        dijit.byId("clearButton").attr("disabled", true);
        break;
    case SearchStatus.NOT_FOUND:
        node.innerHTML = "not found";
        dojo.removeClass(node, "invisible");
        dijit.byId("searchBox").attr("disabled", false);
        dijit.byId("includeDescriptionCheckBox").attr("disabled", false);
        dijit.byId("findNextButton").attr("disabled", hasValue ? false : true);
        dijit.byId("findAllButton").attr("disabled", hasValue ? false : true);
        dijit.byId("clearButton").attr("disabled", hasValue ? false : true);
        break;
    default:
        node.innerHTML = "";
        dojo.addClass(node, "invisible");
        dijit.byId("searchBox").attr("disabled", false);
        dijit.byId("includeDescriptionCheckBox").attr("disabled", false);
        dijit.byId("findNextButton").attr("disabled", hasValue ? false : true);
        dijit.byId("findAllButton").attr("disabled", hasValue ? false : true);
        dijit.byId("clearButton").attr("disabled", hasValue ? false : true);
    }
}


//////////////////////////////////////////////////////////
// TreePathManager
//////////////////////////////////////////////////////////

/**
 * TreePathManager is in charge of finding next matched node in a tree.
 */
var TreePathManager = {
    verbose: false, // verbose console log output
    tree: null,
    pathMap: {}, // map: path -> item
    init: function(tree) {
        this.tree = tree;
        var rootItem = this.tree.model.root;
        this.makePathMap(rootItem, []);
    },
    getIdentity: function(item) {
        var tree = this.tree;
        var identity = tree.model.getIdentity(item);
        return identity + "";
    },
    getPath: function(treeNode) {
        var tree = this.tree;
        var path = [];

        while (treeNode && treeNode.domNode && treeNode !== tree.rootNode) {
            path.unshift(this.getIdentity(treeNode.item));
            treeNode = treeNode.getParent();
        }
        path.unshift(this.getIdentity(tree.rootNode.item));

        return path;
    },
    isEmptyPath: function(path) {
        return (path && path.length > 0) === false;
    },
    getItem: function(path) {
        var pathKey = path.join("/");
        var item = this.pathMap[pathKey] || null;

        return item;
    },
    getNextSiblingPath: function(path) {
        var item = this.getItem(path);
        var parentPath = path.slice(0);
        parentPath.pop();
        if (this.isEmptyPath(parentPath)) {
            return parentPath;
        }

        var parentItem = this.getItem(parentPath);
        var index = dojo.indexOf(parentItem.children, item);
        var siblingIndex = index + 1;

        if (siblingIndex < parentItem.children.length) {
            var siblingItem = parentItem.children[siblingIndex];
            var identity = this.getIdentity(siblingItem);
            var siblingPath = parentPath.slice(0);
            siblingPath.push(identity);
            return siblingPath;
        }

        return [];
    },
    visitTreeItem: function(item, level, callback) {
        // current item
        callback && callback(item, level);

        // walk in children
        var hasChildren = dojo.isArray(item.children) && item.children.length > 0;
        if (hasChildren) {
            var childLevel = level + 1;
            for (var idx in item.children) {
                var childItem = item.children[idx];
                this.visitTreeItem(childItem, childLevel, callback);
            }
        }
    },
    addToPathMap: function(/*String[]*/ path, item) {
        var pathKey = path.join("/");
        this.pathMap[pathKey] = item;
        //console.debug(pathKey, item);
    },
    makePathMap: function(item, /*String[]*/ path) {
        // save current node path
        var model = this.tree.model;
        var identity = model.getIdentity(item);
        path.push(identity);
        this.addToPathMap(path, item);

        // search in children
        for (var idx in item.children) {
            // start a new branch of trace, starting with what we have so far
            var pathBranch = path.slice(0);
            var childItem = item.children[idx];
            this.makePathMap(childItem, pathBranch);
        }
    },
    isMatchPath: function(path, regExp, includeDescription) {
        var item = this.getItem(path);
        return this.isMatchItem(item, regExp, includeDescription);
    },
    isMatchItem: function(item, regExp, includeDescription) {
        var store = this.tree.model.store;
        if (!store.isItem(item)) {
            return false;
        }

        var name = store.getValue(item, "name", null);
        var description = store.getValue(item, "description", null);

        var result = (regExp.test(name) === true);
        if (includeDescription) {
            result = result || (regExp.test(description) === true);
        }

        return result;
    }
};


//////////////////////////////////////////////////////////
// DeferredStep
//////////////////////////////////////////////////////////

/**
 * DeferredStep class makes it easy to chain the asynchronous functions.
 * 
 * @param id
 *          The id of this DeferredStep instance.
 * @param run
 *          The function to run.
 */
function DeferredStep(id, run) {
    this.id = id;
    this.run = run;
    this.def = new dojo.Deferred();
    this.setNextStep = function(nextStep) {
        // clear current callbacks
        this.def.chain = [];

        var callback = dojo.hitch(nextStep, nextStep.run);
        this.def.addCallbacks(callback, function(err) {
            var treeOverlay = dijit.byId("treeOverlay");
            if (!treeOverlay) {
                return;
            }

            treeOverlay.hide();
        });
    };
};

/**
 * The functions for DeferredStep.
 */
var DeferredStepFuncPool = {};

/**
 * Chain DeferredStep instances together and run them step by step.
 * 
 * @param stepNames
 *          The step name in the function pool.
 */
function runSteps(/*String[]*/ stepNames) {
    var id = 1;
    var stepName = stepNames.shift();
    var stepFunc = DeferredStepFuncPool[stepName];
    var step = new DeferredStep(id, stepFunc);
    var firstStep = step;

    // chain steps
    while (stepNames.length > 0) {
        var nextStepName = stepNames.shift();
        var nextStepFunc = DeferredStepFuncPool[nextStepName];
        if (!nextStepFunc) {
            break;
        }

        id++;
        var nextStep = new DeferredStep(id, nextStepFunc);
        step.setNextStep(nextStep);
        step = nextStep;
    }


    var lastStep = nextStep;
    var def = lastStep.def;

    // run chain
    firstStep.run();

    return def;
}

//////////////////////////////////////////////////////////
// DeferredStep functions
//////////////////////////////////////////////////////////
/**
 * Show the tree overlay.
 */
DeferredStepFuncPool["show-overlay"] = function() {
    console.debug("step", this.id, ":", "show-overlay");

    var _self = this;
    var def = _self.def;

    var treeOverlay = dijit.byId("treeOverlay");
    if (!treeOverlay) {
        return;
    }

    treeOverlay.show();

    // do next step
    def.callback();
};

/**
 * Hide the tree overlay.
 */
DeferredStepFuncPool["hide-overlay"] = function() {
    console.debug("step", this.id, ":", "hide-overlay");

    var _self = this;
    var def = _self.def;

    var treeOverlay = dijit.byId("treeOverlay");
    if (!treeOverlay) {
        return;
    }

    treeOverlay.hide();

    // do next step
    def.callback();
};

/**
 * Close the target component detail info panel.
 */
DeferredStepFuncPool["close-info-panel"] = function() {
    console.debug("step", this.id, ":", "close-info-panel");

    var _self = this;
    var def = _self.def;

    var componentInfoPanel = dijit.byId("componentInfo");
    if (componentInfoPanel && componentInfoPanel.attr("open") === true) {
        componentInfoPanel.toggle();
    }

    // do next step
    def.callback();
};

/**
 * Reset the data store of the tree.
 */
DeferredStepFuncPool["reset-store"] = function() {
    console.debug("step", this.id, ":", "reset-store");

    var _self = this;
    var def = _self.def;

    var tree = dijit.byId("tree");
    if (!tree) {
        return;
    }
    var store = tree.model.store;
    store.revert();

    // do next step
    def.callback();
};

/**
 * Refresh the tree.
 */
DeferredStepFuncPool["refresh-tree"] = function() {
    console.debug("step", this.id, ":", "refresh-tree");

    var _self = this;
    var def = _self.def;

    var tree = dijit.byId("tree");
    if (!tree) {
        return;
    }
    var refreshDef = tree.refresh();
    refreshDef.addCallback(function() {
        // do next step
        def.callback();
    });
};

/**
 * Focus the first node in the tree.
 */
DeferredStepFuncPool["focus-first-node"] = function() {
    console.debug("step", this.id, ":", "focus-first-node");

    var _self = this;
    var def = _self.def;

    var tree = dijit.byId("tree");
    if (!tree) {
        return;
    }

    var node = tree._getRootOrFirstNode();
    if (node) {
        tree.focusNode(node);
    }

    // do next step
    def.callback();
};

/**
 * Expand all nodes of the tree.
 */
DeferredStepFuncPool["expand-all"] = function() {
    console.debug("step", this.id, ":", "expand-all");

    var _self = this;
    var def = _self.def;

    var tree = dijit.byId("tree");
    if (!tree) {
        return;
    }

    var expandDef = tree.expandAll();
    expandDef.addCallback(function() {
        // do next step
        def.callback();
    });
};

/**
 * Filter out the non-active target components.
 */
DeferredStepFuncPool["filter-inactive"] = function() {
    console.debug("step", this.id, ":", "filter-inactive");

    var _self = this;
    var def = _self.def;

    var tree = dijit.byId("tree");
    if (!tree) {
        return;
    }

    var store = tree.model.store;
    var query = "NOT status:'A'";

    doFetchStore(def, store, query, function(items) {
        if (dojo.isArray(items)) {
            dojo.forEach(items, function(item) {
                if (TreePathManager.verbose) {
                    var identity = store.getIdentity(item);
                    console.debug("[inactive]", identity, "will be deleted");
                }

                store.deleteItem(item);
            });
        }

        // do next step
        def.callback();
    });
};

/**
 * Filter out the unmatched target components.
 */
DeferredStepFuncPool["filter-unmatches"] = function() {
    console.debug("step", this.id, ":", "filter-unmatches");

    var _self = this;
    var def = _self.def;

    var tree = dijit.byId("tree");
    if (!tree) {
        return;
    }
    var store = tree.model.store;
    var keyword = dijit.byId("searchBox").attr("value");
    if (!keyword) {
        return;
    }

    var includeDescription = dijit.byId("includeDescriptionCheckBox").attr("checked") || false;
    var regExp = new RegExp(keyword, "i");

    var _filterUnmatchedLeafItems = function() {

        doFetchStore(def, store, null, function(items) {
            if (dojo.isArray(items) === false || items.length === 0) {
                // means the end of the search
                def.callback();
                return;
            }

            if (dojo.isArray(items) && items.length > 0) {
                // find all no matched leaf nodes
                var notMatchItems = dojo.filter(items, function(item) {
                    var hasChildren = ("children" in item) ? true : false;
                    var match = TreePathManager.isMatchItem(item, regExp, includeDescription);

                    return !hasChildren && !match;
                });

                if (notMatchItems.length === 0) {
                    // means the end of the search
                    def.callback();
                    return;
                }

                if (dojo.isArray(notMatchItems) && notMatchItems.length > 0) {
                    // delete all not matched items
                    dojo.forEach(notMatchItems, function(notMatchItem) {
                        var identity = store.getIdentity(notMatchItem);
                        var name = store.getValue(notMatchItem, "name");
                        store.deleteItem(notMatchItem);
                        if (TreePathManager.verbose) {
                            console.debug("[leaf]", identity, "has been deleted");
                        }
                    });
    
                    // recursively call next filter
                    _filterUnmatchedLeafItems();
                }
            }
        });
    };

    _filterUnmatchedLeafItems();
};

/**
 * Clear the matched marks for all the items.
 */
DeferredStepFuncPool["unmark-items"] = function() {
    console.debug("step", this.id, ":", "unmark-items");

    var _self = this;
    var def = _self.def;

    var tree = dijit.byId("tree");
    if (!tree) {
        return;
    }
    var store = tree.model.store;

    doFetchStore(def, store, null, function(items) {
        if (dojo.isArray(items)) {
            // set match attribute
            dojo.forEach(items, function(item) {
                store.setValueSilently(item, "match", false);
            });
        }

        // do next step
        def.callback();
    });
};

/**
 * Set the matched mark to highlight the matched target components.
 */
DeferredStepFuncPool["highlight-matches"] = function() {
    console.debug("step", this.id, ":", "highlight-matches");

    var _self = this;
    var def = _self.def;

    var tree = dijit.byId("tree");
    if (!tree) {
        return;
    }
    var store = tree.model.store;

    var keyword = dijit.byId("searchBox").attr("value");
    if (!keyword) {
        return;
    }

    var includeDescription = dijit.byId("includeDescriptionCheckBox").attr("checked") || false;
    var regExp = new RegExp(keyword, "i");

    doFetchStore(def, store, null, function(items) {
        if (dojo.isArray(items)) {
            // find all nodes which match the query
            var matchItems = dojo.filter(items, function(item) {
                var match = TreePathManager.isMatchItem(item, regExp, includeDescription);
                return match;
            });

            // notify results number
            console.info("matches:", matchItems.length);

            // set match attribute
            dojo.forEach(matchItems, function(item) {
                store.setValueSilently(item, "match", true);
            });
        }

        // do next step
        def.callback();
    });
};

/**
 * Display 'searching' string below the search box.
 */
DeferredStepFuncPool["display-searching"] = function() {
    console.debug("step", this.id, ":", "display-searching");

    var _self = this;
    var def = _self.def;

    setSearchStatus(SearchStatus.SEARCHING);

    // do next step
    def.callback();
};

/**
 * Display 'not found' string below the search box if no search results.
 */
DeferredStepFuncPool["display-search-results"] = function() {
    console.debug("step", this.id, ":", "display-search-results");

    var _self = this;
    var def = _self.def;

    var tree = dijit.byId("tree");
    if (!tree) {
        return;
    }
    var store = tree.model.store;

    var query = {top : true};
    doFetchStore(def, store, query, function(items) {
        if (dojo.isArray(items) && items.length > 0) {
            // at least one top node exists, which means it found some
            setSearchStatus(SearchStatus.DEFAULT);
        } else {
            // empty tree, nothing found
            setSearchStatus(SearchStatus.NOT_FOUND);
        }

        // do next step
        def.callback();
    });
};


//////////////////////////////////////////////////////////
// OPC Group
//////////////////////////////////////////////////////////

/**
 * Get the selected group id from the group selector.
 */
function getGroup() {
    var group = null;

    var groupSelector = dijit.byId("opcGroup");
    var groupItem = groupSelector.attr("item");
    if (!groupItem) {
        return null;
    }

    var store = groupSelector.store;
    group = store.getIdentity(groupItem);

    return group;
}

/**
 * Get the checked status of show active only checkbox.
 * true means only show the active target components.
 * false means show all target components.
 */
function isShowActiveOnly() {
    var showActiveOnlyCheckBox = dijit.byId("showActiveOnlyCheckBox");
    var showActiveOnly = showActiveOnlyCheckBox.attr("checked");

    return showActiveOnly;
}

/**
 * Update the data store of group selector for the supplied division ID.
 */
function updateGroupSelector(division) {
    var groupSelector = dijit.byId("opcGroup");

    var overlay = dijit.byId("opcGroupOverlay");
    overlay.show();

    var callback = function(groups) {
        var groupStore = createStore(groups);
        groupSelector.attr("store", groupStore);
        groupSelector.attr("disabled", false);
        groupSelector.reset();
        overlay.hide();
    };

    getGroups(division, callback);
}

/**
 * Get the OPC groups from the data service.
 * And invoke the callback when it gets the items.
 */
function getGroups(division, callback) {
    var groups = [];
    var group = null;
    for (var i = 1; i <= 10; i++) {
        group = {};
        group.moniker = division + "-group_" + i;
        group.name = division + " group" + i;
        group.type = "group";
        group.description = "description -> " + group.name;
        groups.push(group);
    }

    // create a deferred
    var deferred = new dojo.Deferred();

    // add callback.
    // TODO: use deferred.then() instead when dojo.version >= 1.5
    deferred.addCallback(callback);

    // set deferred to fire in 1 second.
    setTimeout(function() {
        deferred.callback(groups);
    }, 1000);
}

/**
 * Show the information of a OPC group.
 */
function showGroupInfo(item, store) {
    dojo.removeClass(dojo.byId("opcGroupDescriptionRow"), "invisible");
    var description = store.getValue(item, "description");
    dojo.byId("opcGroupDescription").innerHTML = description;
}

//////////////////////////////////////////////////////////
// OPC Target Component
//////////////////////////////////////////////////////////

/**
 * Update the target component tree for the supplied group ID.
 */
function updateTargetComponentTree(group) {
    dojo.removeClass(dijit.byId("targetComponentContainer").domNode, "displayNone");

    // loading overlay for tree
    var treeOverlay = dijit.byId("treeOverlay");
    if (!treeOverlay) {
        treeOverlay = new dojox.widget.Standby({
            id: "treeOverlay",
            target: "treePane",
            color: "#FFFFE0",
            centerIndicator: "text",
            text: 'Loading...'
        });
        document.body.appendChild(treeOverlay.domNode);
        treeOverlay.startup();
    }

    treeOverlay.show();

    var callback = function(components) {
        var componentStore = createStore(components);

        var modelArgs = {
            store: componentStore,
            query: { top: true },
            queryOptions: {
                deep: true
            },
            rootId: "$root$",
            rootLabel: "ROOT",
            childrenAttrs: ["children"],
            deferItemLoadingUntilExpand: true
        };

        var tree = dijit.byId("tree");
        if (!tree) {
            tree = new dijit.Tree({
                id: "tree",
                model: new dijit.tree.ForestStoreModel(modelArgs),
                showRoot: false,
                persist: false,
                getIconClass: function(item, opened) {
                    var iconClass = (!item || this.model.mayHaveChildren(item)) ? (opened ? "FolderOpenedIcon" : "FolderClosedIcon") : "FolderClosedIcon";

                    if (item === this.model.root) {
                        return iconClass;
                    }
                    
                    var store = this.model.store;
                    if (item) {
                        var selectable = store.getValue(item, "selectable", false);
                        iconClass = (selectable === true) ? "SelectableComponent" : "NonSelectableComponent";
                    }
                    
                    return iconClass;
                },
                getLabelClass: function(item, opened) {
                    var labelClass = "";

                    if (item === this.model.root) {
                        return labelClass;
                    }

                    var store = this.model.store;
                    if (item) {
                        var status = store.getValue(item, "status");
                        labelClass = (status === "A") ? "" : "InactiveComponent";

                        var match = store.getValue(item, "match", false);
                        labelClass += (match ? " highlight" : "");
                    }

                    return labelClass;
                },
                refresh: function() {
                    // remove the rootNode
                    if (this.rootNode) {
                        this.rootNode.destroyRecursive();
                    }

                    // reset the state of the rootNode
                    this.rootNode.state = "UNCHECKED";

                    // clear nodes cache
                    this._itemNodesMap = {};

                    // load root node
                    var refreshDeferred = new dojo.Deferred();
                    this.model.getRoot(
                        dojo.hitch(this, function(item) {
                            var rn = (this.rootNode = this.tree._createTreeNode({
                                item: item,
                                tree: this,
                                isExpandable: true,
                                label: this.label || this.getLabel(item),
                                indent: this.showRoot ? 0 : -1
                            }));
                            if(!this.showRoot){
                                rn.rowNode.style.display="none";
                            }
                            this.domNode.appendChild(rn.domNode);
                            var identity = this.model.getIdentity(item);
                            if(this._itemNodesMap[identity]){
                                this._itemNodesMap[identity].push(rn);
                            }else{
                                this._itemNodesMap[identity] = [rn];
                            }

                            rn._updateLayout();     // sets "dijitTreeIsRoot" CSS classname

                            // update top level nodes
                            this.model.store.fetch({
                                query: this.model.query,
                                onComplete: dojo.hitch(this, function(newChildren) {
                                    this.model.root.children = newChildren;

                                    // expand root node
                                    this._expandNode(rn).addCallback(dojo.hitch(this, function() {
                                        //this.onLoad();
                                        refreshDeferred.callback(true);
                                    }));
                                })
                            });
                        }),
                        function(err) {
                            console.error(this, ": error loading root: ", err);
                            refreshDeferred.errback(err);
                        }
                    );

                    return refreshDeferred;
                },
                expandAll: function() {
                    // summary:
                    //     Expand all nodes in the tree
                    // returns:
                    //     Deferred that fires when all nodes have expanded
                    // see: http://stackoverflow.com/questions/2161032/expanding-all-nodes-in-dijit-tree

                    var _this = this;

                    var _expand = function(node) {
                        var def = new dojo.Deferred();

                        // Expand the node
                        _this._expandNode(node).addCallback(function() {
                            // When node has expanded, call _expand() recursively on each non-leaf child
                            var childBranches = dojo.filter(node.getChildren() || [], function(node) {
                                return node.isExpandable;
                            });

                            var defs = dojo.map(childBranches, _expand);

                            // And when all those recursive calls finish, signal that I'm finished
                            new dojo.DeferredList(defs).addCallback(function() {
                                def.callback();
                            });
                        });

                        return def;
                    };

                    return _expand(this.rootNode);
                },
                collapseAll: function() {
                    // summary:
                    //     Collapse all nodes in the tree
                    // returns:
                    //     Deferred that fires when all nodes have collapsed

                    var _this = this;

                    var _collapse = function(node) {
                        var def = new dojo.Deferred();

                        // call _collapse() recursively on each non-leaf child 
                        var childBranches = dojo.filter(node.getChildren() || [], function(node) {
                            return node.isExpandable;
                        });

                        var defs = dojo.map(childBranches, _collapse);

                        // When all children nodes have collapsed, collapse on this node.
                        new dojo.DeferredList(defs).addCallback(function() {

                            // But never collapse root node, otherwise hides whole tree!
                            if (_this.showRoot === false && node !== _this.rootNode) {
                                _this._collapseNode(node);
                            }

                            // signal that I'm finished
                            setTimeout(function() {
                                def.callback();
                            }, 300);
                        });

                        return def;
                    };

                    return _collapse(this.rootNode);
                },
                _onItemDelete: function(/*Item*/ item) {
                    // summary:
                    //      Processes notification of a deletion of an item
                    var model = this.model,
                        identity = model.getIdentity(item),
                        nodes = this._itemNodesMap[identity];

                    if (nodes) {
                        dojo.forEach(nodes, function(node) {
                            if (node.domNode) {
                                var parent = node.getParent();
                                if (parent) {
                                    // if node has not already been orphaned from a _onSetItem(parent, "children", ..) call...
                                    parent.removeChild(node);
                                }
                            }
                            node.destroyRecursive();
                        });
                        delete this._itemNodesMap[identity];
                    }
                }
            }, dojo.byId("tree"));

            //_onNodeFocus
            dojo.connect(tree, "focusNode", function(node) {
                var store = this.model.store;
                if (node && node.item) {
                    showTargetComponentInfo(node.item, store);
                }
            });
        } else {
            // refresh tree from new data store
            tree.model.destroy();
            tree.model.constructor(modelArgs);
        }

        // refresh target component tree
        var def = refreshTargetComponentTree();
    };

    getTargetComponents(group, callback);
}

/**
 * Get the OPC target components from the data service.
 * And invoke the callback when it gets the items.
 */
function getTargetComponents(groupId, callback) {
    var components = [];

    var group = "[" + groupId + "]";
    var _createItem = function(id, level, itemMap) {
        var item = createComponent(group, id);
        if (level == 0) {
            item.top = true;
        }

        // create children
        if (level < 3) {
            var childCount = demo.utils.randomInt(0, 5);
            if (childCount > 0) {
                item.children = [];
            }

            for (var i = 0; i < childCount; i++) {
                var childItem = _createItem(id + "-" + i, level + 1, itemMap);

                item.children.push({
                    _reference: childItem.moniker
                });
            }
        }

        // add to item map
        itemMap[item.moniker] = item;

        return item;
    };

    var itemMap = {};
    for (var i = 0; i < 10; i++) {
        _createItem(i, 0, itemMap);
    }

    for (var key in itemMap) {
        var component = itemMap[key];
        components.push(component);
    }

    // create a deferred
    var deferred = new dojo.Deferred();

    // add callback.
    // TODO: use deferred.then() instead when dojo.version >= 1.5
    deferred.addCallback(callback);

    // set deferred to fire in 1 second.
    setTimeout(function() {
        var items = [
            {"moniker": "1", "name": "Item 1", "type": "R", "selectable": true, "status": "I", "code": "", "description": "", "top": true, "children": [{"_reference": "1-1"}]},
            {"moniker": "1-1", "name": "Item 1-1", "type": "U", "selectable": true, "status": "A", "code": "", "description": "", "children": [{"_reference": "1-1-1"}]},
            {"moniker": "1-1-1", "name": "Item 1-1-1", "type": "R", "selectable": true, "status": "I", "code": "", "description": ""},
            {"moniker": "2", "name": "Item 2", "type": "U", "selectable": true, "status": "A", "code": "", "description": "", "top": true, "children": [{"_reference": "2-1"}]},
            {"moniker": "2-1", "name": "Item 2-1", "type": "R", "selectable": true, "status": "A", "code": "", "description": "", "children": [{"_reference": "2-1-1"}]},
            {"moniker": "2-1-1", "name": "Item 2-1-1", "type": "R", "selectable": true, "status": "C", "code": "", "description": ""}
        ];
        //components = items;
        deferred.callback(components);
    }, 1000);
}

/**
 * Create a target component item.
 * @param {Object} group
 * @param {Object} id
 */
function createComponent(group, id) {
    var randomInt = demo.utils.randomInt(0, 100);

    var component = {};
    component.moniker = id;
    component.name = group + " component " + id;
    component.type = (randomInt%7 === 0) ? "U" : "R";
    component.selectable = (randomInt%5 === 0) ? false : true;
    component.code = "luj" + randomInt;
    component.description = "description -> " + component.name;

    var status = null;
    switch (randomInt%5) {
        case 0:
            status = "A";
            break;
        case 1:
            status = "C";
            break;
        case 2:
            status = "F";
            break;
        case 3:
            status = "I";
            break;
        case 4:
            status = "H";
            break;
        default:
    }

    component.status = status;

    return component;
}

/**
 * Show the information of a OPC target component.
 */
function showTargetComponentInfo(item, store) {
    // open info panel
    var componentInfoPanel = dijit.byId("componentInfo");
    if (componentInfoPanel) {
        var open = componentInfoPanel.attr("open");

        // open panel
        if (open === true) {
//            // close first
//            componentInfoPanel.toggle();
//
//            // open again
//            setTimeout(function() {
//                if (componentInfoPanel.attr("open") === false) {
//                    componentInfoPanel.toggle();
//                }
//            }, 300);
        } else {
            // open
            componentInfoPanel.toggle();
        }

        // change background color
        var highlightAnim = dojo.animateProperty({
            node: componentInfoPanel.containerNode,
            duration: 1000,
            properties: {
                backgroundColor: { start: "#FFFFE0", end: "" }
            }
        });
        highlightAnim.play();
    }

    var name = store.getValue(item, "name");
    dojo.byId("componentName").innerHTML = name;

    var type = store.getValue(item, "type");
    dojo.byId("componentType").innerHTML = formatTargetComponentType(type);

    var selectable = store.getValue(item, "selectable");
    dojo.byId("componentSelectable").innerHTML = formatTargetComponentSelectable(selectable);

    var status = store.getValue(item, "status");
    dojo.byId("componentStatus").innerHTML = formatTargetComponentStatus(status);

    var code = store.getValue(item, "code");
    dojo.byId("componentCode").innerHTML = code;

    var description = store.getValue(item, "description");
    dojo.byId("componentDescription").innerHTML = description;
}

/**
 * Refresh the target component tree.
 */
function refreshTargetComponentTree() {
    var steps = [];

    // Step 1: close target component detail info panel
    steps.push("close-info-panel");

    // Step 2: show overlay
    steps.push("show-overlay");

    // Step 3: reset store
    steps.push("reset-store");

    // Step 4: unmark items
    // make a snapshot of data store for next revert
    steps.push("unmark-items");

    // Step 5: refresh tree
    // delete items will notify tree to delete the nodes,
    // we have to make sure tree nodes are up-to-date before deleting
    steps.push("refresh-tree");

    var showActiveOnly = isShowActiveOnly();
    if (showActiveOnly) {
        // Step 6: filter inactive items
        steps.push("filter-inactive");
    }

    // Step 7: focus first node in the tree
    steps.push("focus-first-node");

    // Step 8: hide overlay
    steps.push("hide-overlay");

    // chain & run
    console.debug("> refresh target components");
    var def = runSteps(steps);

    return def;
}


///////////////////////////////////////////////////////////////////
// Search
///////////////////////////////////////////////////////////////////

/**
 * Find next matched node in the target component tree.
 */
function findNext() {
    var keyword = dijit.byId("searchBox").attr("value");
    if (!keyword) {
        return;
    }

    var includeDescription = dijit.byId("includeDescriptionCheckBox").attr("checked") || false;
    debug("> find next", includeDescription ? "name+desc:" : "name:", keyword);

    var regExp = new RegExp(keyword, "i");
    var tree = dijit.byId("tree");

    // init tree path manager
    TreePathManager.init(tree);

    var startNode = tree.lastFocused;
    var startPath = TreePathManager.getPath(startNode);

    // walk to next node recursively
    var walkToNextNode = function(/*String[]*/path) {
        // jump to first node if necessary
        if (TreePathManager.isEmptyPath(path)) {
            var node = tree._getRootOrFirstNode();
            path = TreePathManager.getPath(node);
            walkToNextNode(path);
            return;
        }

        path = path.slice(0); // make a copy

        var item = TreePathManager.getItem(path);

        if (dojo.isArray(item.children) && item.children.length > 0) {
            // if this is a parent, get the first child
            item = item.children[0];
            var identity = TreePathManager.getIdentity(item);
            path.push(identity);
        } else {
            // find a parent path with a sibling
            while (!TreePathManager.isEmptyPath(path)) {
                var siblingPath = TreePathManager.getNextSiblingPath(path);

                // sibling is found, skip loop
                if (!TreePathManager.isEmptyPath(siblingPath)) {
                    path = siblingPath;
                    break;
                }

                // jump to parent path
                path.pop();
            }
        }

        // check for last node, jump to first node if necessary
        if (TreePathManager.isEmptyPath(path)) {
            var node = tree._getRootOrFirstNode();
            if (!node) {
                setSearchStatus(SearchStatus.NOT_FOUND);
                return;
            }

            path = TreePathManager.getPath(node);
        }

        if (TreePathManager.isMatchPath(path, regExp, includeDescription)) {
            tree.attr("path", path);
            setSearchStatus(SearchStatus.DEFAULT);
        } else if (dojo.toJson(path) === dojo.toJson(startPath)) {
            setSearchStatus(SearchStatus.NOT_FOUND);
        } else {
            walkToNextNode(path);
        }
    };

    // find next
    walkToNextNode(startPath);
}

/**
 * Find all matched nodes in the target component tree.
 * It will filter the nodes and highlight all the matched nodes.
 */
function findAll() {
    var keyword = dijit.byId("searchBox").attr("value");
    if (!keyword) {
        return;
    }

    var regExp = new RegExp(keyword, "i");
    var includeDescription = dijit.byId("includeDescriptionCheckBox").attr("checked") || false;

    debug("> find all", includeDescription ? "name+desc:" : "name:", keyword);

    // init tree path manager
    var tree = dijit.byId("tree");
    TreePathManager.init(tree);

    var steps = [];

    // Step 1: close target component detail info panel
    steps.push("close-info-panel");

    // Step 2: show 'searching' text
    steps.push("display-searching");

    // Step 3: show overlay
    steps.push("show-overlay");

    // Step 4: reset store
    steps.push("reset-store");

    // Step 5: unmark items
    // make a snapshot of data store for next revert
    steps.push("unmark-items");

    // Step 6: mark search results
    steps.push("highlight-matches");

    // Step 7: refresh tree
    steps.push("refresh-tree");

    // Step 8: expand all nodes
    steps.push("expand-all");

    var showActiveOnly = isShowActiveOnly();
    if (showActiveOnly) {
        // Step 9: filter inactive items
        steps.push("filter-inactive");
    }

    // Step 10: filter unmatches
    steps.push("filter-unmatches");

    // Step 11: show 'not found' if no matches
    steps.push("display-search-results");

    // Step 12: focus first node in the tree
    steps.push("focus-first-node");

    // Step 13: hide overlay
    steps.push("hide-overlay");

    // chain & run
    runSteps(steps);
}

/**
 * Clear search results.
 */
function clearResults() {
    dijit.byId("searchBox").reset();
    dijit.byId("includeDescriptionCheckBox").reset();
    refreshTargetComponentTree();
}

///////////////////////////////////////////////////////////////////////////////

function findNextByExpandTree() {
    var keyword = dijit.byId("searchBox").attr("value");
    if (!keyword) {
        return;
    }
    var includeDescription = dijit.byId("includeDescriptionCheckBox").attr("checked") || false;
    debug("find next", includeDescription ? "name+desc:" : "name:", keyword);

    var regExp = new RegExp(keyword, "i");
    var tree = dijit.byId("tree");
    var store = tree.model.store;
    var _isMatchKeyword = function(node, regExp) {
        var item = node.item;
        var name = store.getValue(item, "name", null);
        var description = store.getValue(item, "description", null);

        var result = (regExp.test(name) === true);
        if (includeDescription) {
            result = result || (regExp.test(description) === true);
        }

        return result;
    };

    var startNode = tree.lastFocused;

    // Navigate to TreeNode matching the keyword.
    var walkToNextNode = function(node, callback) {
        // check for last node, jump to first node if necessary
        if (!node) {
            node = tree._getRootOrFirstNode();
            walkToNextNode(node, callback);
            return;
        }

        // if this is an collapsed node, expand it
        if (node.isExpandable && node.isExpanded === false) {
            var def = tree._expandNode(node);
            def.addCallback(function() {
                walkToNextNode(node, callback);
            });
            return;
        }

        node = tree._getNextNode(node);
        if (!node) {
            node = tree._getRootOrFirstNode();
        }

        if (_isMatchKeyword(node, regExp)) {
            tree.focusNode(node);
            setSearchStatus(SearchStatus.DEFAULT);
        } else if (node === startNode) {
            setSearchStatus(SearchStatus.NOT_FOUND);
        } else {
            walkToNextNode(node, callback);
        }
    };

    setSearchStatus(SearchStatus.SEARCHING);

    // expand all, then find next
    var def = tree.expandAll();
    def.addCallback(function() {
        walkToNextNode(startNode);
    });
}

function _findPath(item, model, trace) {
    // save current node path
    var id = model.getIdentity(item);
    trace.push(id);

    // found, then return
    if (_isMatch(item, regExp)) {
        return trace;
    }

    // search in children
    for (var idx in item.children) {
        // start a new branch of trace, starting with what we have so far
        var traceBranch = trace.slice(0);
        var childItem = item.children[idx];
        var pathBranch = _findPath(childItem, model, traceBranch);

        // found in branch
        if (pathBranch) {
            return pathBranch;
        }
    }

    // return null, indicating no match was found
    return null;
}

function focusNode(path) {
    var tree = dijit.byId("tree");
    tree.attr("path", path);
}

function printTree() {
    var tree = dijit.byId("tree");
    if (!tree) {
        return;
    }

    // init tree path manager
    TreePathManager.init(tree);

    // TODO:
    var rootItem = tree.model.root;
    var model = tree.model;
    TreePathManager.visitTreeItem(rootItem, 0, function(item, level) {
        var identity = model.getIdentity(item);
        var indentation = dojo.string.rep(" ", 3 * level);
        console.debug(indentation + "|-", identity);
    });

}
</script>
</head>
<body class="tundra">

  <div id="main" class="container">
    <div id="titlePanel" dojoType="dijit.TitlePane" title="Panel" style="">
      <div id="resizablePanel" style="position:relative; padding: 10px; border:1px solid black;">

        <!-- Content -->
        <div class="content">
          <table>
            <!--- OPC division -->
            <tr class="common hidden">
              <td nowrap="nowrap" valign="middle" style="padding: 0.2em 0.5em 0.2em 0;">
                <span style="white-space: nowrap;"><label for="opcDivision">OPC division:</label></span>
              </td>
              <td nowrap="nowrap" valign="middle" style="padding: 0.2em 0;" width="100%">
                <input id="opcDivision" dojoType="dijit.form.ComboBox" type="text" searchAttr="name" style="width: 200px" value="-- Select a division --" />
              </td>
            </tr>
            <!-- OPC group -->
            <tr class="common hidden">
              <td nowrap="nowrap" valign="middle" style="padding: 0.2em 0.5em 0.2em 0;">
                <span style="white-space: nowrap;"><label for="opcGroup">OPC group:</label></span>
              </td>
              <td nowrap="nowrap" valign="middle" style="padding: 0.2em 0;">
                <input id="opcGroup" dojoType="dijit.form.ComboBox" type="text" searchAttr="name" style="width: 200px" value="-- Select a group --" disabled="disabled" />
                <div id ="opcGroupOverlay" dojoType="dojox.widget.Standby" target="opcGroup" color="#FFFFE0" centerIndicator="text" text="Loading..."></div>
              </td>
            </tr>
            <!-- show active only -->
            <tr class="common hidden">
              <td nowrap="nowrap" valign="middle" style="padding: 0.2em 0.5em 0.2em 0;">
              </td>
              <td nowrap="nowrap" valign="middle" style="padding: 0.2em 0;">
                <input id="showActiveOnlyCheckBox" dojoType="dijit.form.CheckBox" type="checkbox" value="true" checked="checked" />
                <label for="showActiveOnlyCheckBox">Show active target components only</label>
              </td>
            </tr>
            <!-- OPC group description -->
            <tr id="opcGroupDescriptionRow" class="common hidden invisible">
              <td nowrap="nowrap" valign="middle" style="padding: 0.2em 0.5em 0.2em 0;">
                <span style="white-space: nowrap;"><label for="opcGroupDescription">OPC group description:</label></span>
              </td>
              <td nowrap="nowrap" valign="middle" style="padding: 0.2em 0;">
                <span id="opcGroupDescription"></span>
              </td>
            </tr>
          </table>
          <br />

          <div id="targetComponentContainer" dojoType="dijit.TitlePane" title="Select target component" toggleable="false">
          <div id="borderContainer" dojoType="dijit.layout.BorderContainer" style="width: 100%; height: 500px;">
            <div id="searchBarPane" dojoType="dijit.layout.ContentPane" region="top">
              <input id="searchBox" dojoType="dijit.form.TextBox" type="text" trim="true" style="width: 200px" />
              <button id="findNextButton" dojoType="dijit.form.Button" disabled="disabled" onClick="findNext();">Find next</button>
              <button id="findAllButton" dojoType="dijit.form.Button" disabled="disabled" onClick="findAll();">Search (find all)</button>
              <button id="clearButton" dojoType="dijit.form.Button" disabled="disabled" onClick="clearResults();">Clear results</button>
              <br />
              <input id="includeDescriptionCheckBox" dojoType="dijit.form.CheckBox" type="checkbox" value="true" />
              <label for="includeDescriptionCheckBox">Search both name and description</label>
              <div>&nbsp;<span id="searchStatus" class="search-status invisible"></span></div>
            </div>

            <div id="treePane" dojoType="dijit.layout.ContentPane" region="center">
              <div style="margin: 0 0.5em 0.5em 0.5em;">
                <div style="float: right;">
                  <a href="javascript://" onclick="dijit.byId('tree').expandAll(); return false;">Expand all</a>
                  &nbsp;
                  <a href="javascript://" onclick="dijit.byId('tree').collapseAll(); return false;">Collapse all</a>
                </div>
                <div style="clear: all;">&nbsp;</div>
              </div>
              <div id="treeContainer" style="overflow: auto; border: 1px solid #CCCCCC; height: 340px; margin: 0.5em; padding: 0.5em;">
                <div id="tree"></div>
              </div>
            </div>
            <div dojoType="dijit.layout.ContentPane" region="trailing" splitter="true" style="width: 450px;">
              <div id="componentInfo" dojoType="dijit.TitlePane" title="Detail information" toggleable="false" open="false">
                <table width="100%" border="1">
                  <tbody>
                    <tr>
                      <td width="100px"><label for="componentName" class="infoLabel">Name:</label></td>
                      <td><span id="componentName"></span></td>
                    </tr>
                    <tr>
                      <td><label for="componentType" class="infoLabel">Type:</label></td>
                      <td><span id="componentType"></span></td>
                    </tr>
                    <tr>
                      <td><label for="componentStatus" class="infoLabel">Status:</label></td>
                      <td><span id="componentStatus"></span></td>
                    </tr>
                    <tr>
                      <td><label for="componentSelectable" class="infoLabel">Selectable:</label></td>
                      <td><span id="componentSelectable"></span></td>
                    </tr>
                    <tr>
                      <td><label for="componentCode" class="infoLabel">Code:</label></td>
                      <td><span id="componentCode"></span></td>
                    </tr>
                    <tr>
                      <td><label for="componentDescription" class="infoLabel">Description:</label></td>
                      <td><span id="componentDescription"></span></td>
                    </tr>
                  </tbody>
                </table>
              </div>
            </div>
            <div dojoType="dijit.layout.ContentPane" region="bottom">
              Bottom pane
            </div>
          </div>
          </div>
        </div>

        <!-- Resize Handle -->
        <div id="resizeHandle" dojoType="dojox.layout.ResizeHandle" targetId="resizablePanel"></div>
      </div>
    </div>

    <!-- Button Panel -->
    <div dojoType="dojo.dnd.Moveable">
      <div id="buttonPanel" dojoType="dijit.TitlePane" title="Button Panel" style="">
        <!-- expand all -->
        <button dojoType="dijit.form.Button" title="expand all" onClick="dijit.byId('tree').expandAll();">Expand all</button>

        <!-- print -->
        <button dojoType="dijit.form.Button" title="print" onClick="printTree();">Print</button>

        <!-- focus node -->
        <button dojoType="dijit.form.Button" title="focus node" onClick="focusNode(['$root$', '0', '0-0']);">focus</button>

        <!-- Button -->
        <button dojoType="dijit.form.Button" title="Click to show popup dialog" onClick="dijit.byId('dialog').show();">Show Popup</button>

        <!-- DropDown Button -->
        <button dojoType="dijit.form.DropDownButton" title="Click to show TooltipDialog">
          <span>DropDownButton with TooltipDialog</span>
          <!-- TooltipDialog -->
          <div dojoType="dijit.TooltipDialog">
            <label for="name">Name:</label>
            <input dojoType="dijit.form.TextBox" id="name" name="name" />
            <br />
            <div dojoType="dijit.form.Button" type="submit" title="Save">Save</div>
          </div>
        </button>

        <button dojoType="dijit.form.DropDownButton" title="Click to show Menu">
          <span>DropDownButton with Menu</span>
          <!-- Menu -->
          <div dojoType="dijit.Menu" id="menu" style="display: none;">
            <div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconCut" onClick="alert('Cut!')">Cut</div>
            <div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconCopy" onClick="alert('Copy!')">Copy</div>
            <div dojoType="dijit.MenuSeparator"></div>
            <div dojoType="dijit.PopupMenuItem">
              <span>Popup ColorPalette</span>
              <div dojoType="dijit.ColorPalette"></div>
            </div>
          </div>
        </button>
      </div>
    </div>
  </div>

  <!-- Debug Panel -->
  <div id="debugPanel" dojoType="dojox.layout.FloatingPane" title="Console" resizable="true" dockable="true" style="position: absolute; bottom: 20px; right: 20px; width: 500px; height: 150px;">
    <pre id="debugPanelContent"></pre>
  </div>

  <!-- Popup Dialog -->
  <div dojoType="dijit.Dialog" id="dialog" title="Dialog" style="display: none;">
    <button id="okButton" dojoType="dijit.form.Button" title="OK"  onClick="alert('OK!');">OK</button>
    <button id="cancelButton" dojoType="dijit.form.Button" title="Cancel" onClick="dijit.byId('dialog').hide();">Cancel</button>
  </div>
</body>
</html>
